﻿// <copyright file="Database.cs" company="Seditious Software">
// Seditious Software 2011, MIT Licenced (http://www.opensource.org/licenses/mit-license)
// </copyright>
// <author>Tristan McNab</author>
// <date>2011-03-04</date>
namespace Seditious.NETMF.MuDB
{
    using System;
    using System.Collections;
    using System.Diagnostics;
    using System.IO;
    using Microsoft.SPOT;
    using VikingErik.NetMF.MicroLinq;

    /// <summary>
    /// Represents a collection of tables and associated support methods.
    /// </summary>
    public class Database : IDisposable
    {
        /// <summary>
        /// Container for table data
        /// </summary>
        private ArrayList tables;         // Table data when cached into memory
        
        #region Birth & Death

        /// <summary>
        /// Initializes a new instance of the Database class. Should be used if creating a new database.
        /// </summary>
        public Database()
        {
            this.tables = new ArrayList();
        }

        /// <summary>
        /// Initializes a new instance of the Database class with data from a stream.
        /// </summary>
        /// <param name="stream">The stream to read database data from.</param>
        /// <exception cref="MuDB.DatabaseException">
        /// This exception will be thrown if the stream does not support reading or seeking,
        /// or the amount of data read does not match the specification.
        /// </exception>
        public Database(Stream stream)
        {
            // Check that we have the right stream
            if (!stream.CanRead || !stream.CanSeek)
            {
                throw new DatabaseException("Provided stream cannot read or seek.");
            }

            // First four bytes are the size of tables ArrayList
            byte[] bits = new byte[sizeof(int)];
            stream.Read(bits, 0, sizeof(int));
            int nbytes = (int)Reflection.Deserialize(bits, typeof(int));
            Debug.GC(true);

            // Deserialize the table data
            byte[] tableData = new byte[nbytes];
            if (stream.Read(tableData, sizeof(int), nbytes) != nbytes)
            {
                throw new DatabaseException("Data in provided stream may be corrupted.");
            }

            this.tables = (ArrayList)Reflection.Deserialize(tableData, typeof(ArrayList));
            Debug.GC(true);
        }

        /// <summary>
        /// Finalizes an instance of the Database class.
        /// </summary>
        ~Database()
        {
            this.Dispose();
        }

        /// <summary>
        /// Disposes the current instance.
        /// </summary>
        public void Dispose()
        {
            Debug.GC(true);
        }

        /// <summary>
        /// Serializes and flushes data to the stream.
        /// </summary>
        /// <param name="stream">The stream to write to.</param>
        public void SaveChanges(Stream stream)
        {
            // Ensure we are working on a valid stream
            if (!stream.CanWrite || !stream.CanSeek)
            {
                throw new DatabaseException("Provided stream cannot write or seek.");
            }

            // Write the data out to the stream
            byte[] data = Reflection.Serialize(this.tables, typeof(ArrayList));
            stream.Write(Reflection.Serialize(data.Length, typeof(int)), 0, sizeof(int));
            stream.Write(data, sizeof(int), data.Length);
            Debug.GC(true);
        }

        #endregion

        #region Debugging Assist Code

        /// <summary>
        /// Prints out the database schema to the debugger.
        /// </summary>
        /// <param name="database">The database to print schema information from.</param>
        [Conditional("DEBUG")]
        public static void DumpSchema(Database database)
        {
            foreach (Table item in database.tables)
            {
                Table.DumpSchema(item);
            }
        }

        #endregion

        #region Table Operations

        /// <summary>
        /// Inserts a new table into the database.
        /// </summary>
        /// <param name="type">The type of object to serve as the schema</param>
        /// <returns>The table that was inserted.</returns>
        /// <exception cref="MuDB.DatabaseException">
        /// This exception will be thrown if the database already contains a definition for 
        /// the provided type.
        /// </exception>
        /// <seealso cref="Contains"/>
        public Table Insert(Type type)
        {
            // Throw an exception if they tried to insert an existing table of Type type
            if ((Table)this.tables.FirstOrDefault(t => ((Table)t).Type == type) != null)
            {
                throw new DatabaseException("Database already contains table '" + type.Name + "'");
            }
            else
            {
                Table newTable = new Table(type);
                this.tables.Add(newTable);
                Debug.GC(true);
                return newTable;
            }
        }

        /// <summary>
        /// Fetches the table defined by the type.
        /// </summary>
        /// <param name="type">The type of table to fetch.</param>
        /// <returns>The table of the type.</returns>
        /// <exception cref="MuDB.DatabaseException">
        /// This exception will be thrown if the database does not contain the provided type.
        /// </exception>
        /// <seealso cref="Contains"/>
        public Table Select(Type type)
        {
            var table = (Table)this.tables.FirstOrDefault(t => ((Table)t).Type == type);

            if (table == null)
            {
                throw new DatabaseException("Database does not contain table '" + type.Name + "'");
            }
            else
            {
                Table newTable = new Table(type);
                this.tables.Add(newTable);
                return newTable;
            }
        }

        /// <summary>
        /// Removes a table from the database.
        /// </summary>
        /// <param name="type">The table to remove from the database</param>
        /// <exception cref="MuDB.DatabaseException">
        /// This exception will be thrown if the database does not contain the provided type.
        /// </exception>
        /// <seealso cref="Contains"/>
        public void Delete(Type type)
        {
            // Fetch the table
            var table = (Table)this.tables.FirstOrDefault(t => ((Table)t).Type == type);

            // Throw an exception if they tried to delete something that doesn't exist
            if (table == null)
            {
                throw new DatabaseException("Database does not contain table '" + type.Name + "'");
            }

            // Perform the delete operation
            this.tables.Remove(table);
        }

        /// <summary>
        /// Determines if the database contains an existing table.
        /// </summary>
        /// <param name="type">The table type to check</param>
        /// <returns>True if it exists, false if it does not exist.</returns>
        public bool Contains(Type type)
        {
            return this.tables.FirstOrDefault(t => ((Table)t).Type == type) != null;
        }

        #endregion
    }
}